using System.Windows.Forms;
using System.Drawing;
using Microsoft.DirectX.Direct3D;
using System;
using System.ComponentModel;
using System.Runtime.InteropServices;
using System.Security;
using System.Threading;
using System.Diagnostics;
using SharpMedia.Input;
using Lizk.SimpleHUD;

namespace SharpMedia.MDX
{
    public class MDXForm : Form, Engine
    {
        public enum EngineState { Lost, Reset, Ready, ShuttingDown }


        protected Size screenSize;
        private Point locationBeforeFullscreen;
        private PresentParameters currentPresentParameters;
        private FormWindowState currentWindowState;
        private long ticksAtLastRender = 0;
        private MouseHider mouseHider;
        private Size windowSize;

        public MDXRenderer Renderer { get; set; }
        public EngineState State { get; private set; }
        public WindowMode Windowed { get; set; }
        public Device GraphicsDevice { get; private set; }
        public AdapterInformation CurrentAdapter { get; private set; }

        public event EventHandler Render;
        public event EventHandler DoUpdate;
        public event EventHandler AfterReset;
        public event EventHandler EndingProgram;
        public event CancelEventHandler BeforeReset;

        public MDXForm(Size windowSize) 
        {
            SetStyle(ControlStyles.UserPaint | ControlStyles.AllPaintingInWmPaint | ControlStyles.Opaque, true);

            this.windowSize = ClientSize = windowSize;
            this.currentWindowState = WindowState;
            
            Device.IsUsingEventHandlers = true;
            Application.Idle += new EventHandler(UpdateLoop);
            this.ResizeEnd += new EventHandler(HandleWindowResize);
            this.FormClosing += new FormClosingEventHandler(MDXForm_FormClosing);
            this.mouseHider = new MouseHider(this);
        }


        protected void HandleWindowResize(object sender, EventArgs args) 
        {
            if (Windowed == WindowMode.Windowed)
            {
                if (windowSize != ClientSize)
                {
                    windowSize = ClientSize;
                    ResetDevice();
                }
            }
            else
            {
                ResetDevice();
            }
        }

        protected override void OnShown(EventArgs e)
        {
            CreatePresentParameters();
            CreateDevice();
            ResetDevice();
            base.OnShown(e);
        }

        void MDXForm_FormClosing(object sender, FormClosingEventArgs e)
        {
            State = EngineState.ShuttingDown;
            if (EndingProgram != null)
                EndingProgram(this, EventArgs.Empty);
        }
        private void UpdateLoop(object sender, EventArgs e)
        {
            while (AppStillIdle)
            {
                if (WindowState != currentWindowState)
                {
                    this.currentWindowState = WindowState;
                    
                    if (Windowed == WindowMode.Windowed) 
                        windowSize = ClientSize;
                    
                    ResetDevice();
                }

                // Update the global timer
                MDXUtil.UpdateTimer();

                MDXUtil.Wait();
                Renderer.HandleVideoCommandQueue();
                MDXUtil.Resume();

                mouseHider.Update(MDXUtil.TimeSpanElapsed);

                // if the form is not focused, save som cpu cycles by delaying a little.
                if (!Focused && !Renderer.VideoPlaying)
                    Thread.Sleep(20);

                if (DoUpdate != null)
                    DoUpdate(this, null);

                
                // Render something black
                if (State == EngineState.Ready && DeviceAvailable())
                {
                    // This part ensures we dont render too many frames, caps the cpu usage, who wants a mediacenter that uses 100% cpu when doing nothing?
                    double elapsed = MDXUtil.CurrentTickCount - ticksAtLastRender;
                    double fps = GraphicsDevice.DisplayMode.RefreshRate * 2;
                    double frameTime = TimeSpan.TicksPerSecond / fps;

                    if (elapsed < frameTime)
                    {
                        Thread.Sleep((int)((frameTime - elapsed) / (double)TimeSpan.TicksPerMillisecond));
                    }


                    GraphicsDevice.BeginScene();

                    if (Render != null)
                        Render(this, null);


                    GraphicsDevice.EndScene();
                    try
                    {
                        GraphicsDevice.Present();
                    }
                    catch (DeviceLostException)
                    {
                        State = EngineState.Lost;
                    }
                }
            }
        }


        /// <summary>
        /// Create the Graphics Device
        /// </summary>
        /// TODO: More Backwards Compatibility
        private void CreateDevice()
        {
            // Try to enable Hardware processing.
            CreateFlags flags = CreateFlags.SoftwareVertexProcessing;
            Caps caps = Manager.GetDeviceCaps(CurrentAdapter.Adapter, DeviceType.Hardware);
            if (caps.DeviceCaps.SupportsHardwareTransformAndLight)
            {
                flags = CreateFlags.HardwareVertexProcessing;
            }

            if (caps.DeviceCaps.SupportsPureDevice)
                flags |= CreateFlags.PureDevice;

            
            flags |= CreateFlags.MultiThreaded;
            
            GraphicsDevice = new Device(CurrentAdapter.Adapter, DeviceType.Hardware, this, flags, currentPresentParameters);
            
            // We want to handle resizing. So do nothing plese.
            GraphicsDevice.DeviceResizing += new CancelEventHandler((sender, e) => e.Cancel = true);

            // try to enable anistopic filtering if possible and enabled.
            if (GraphicsDevice.DeviceCaps.TextureFilterCaps.SupportsMagnifyAnisotropic)
                GraphicsDevice.SamplerState[0].MagFilter = TextureFilter.Anisotropic;
            else if (GraphicsDevice.DeviceCaps.TextureFilterCaps.SupportsMagnifyLinear)
                GraphicsDevice.SamplerState[0].MagFilter = TextureFilter.Linear;

            if (GraphicsDevice.DeviceCaps.TextureFilterCaps.SupportsMinifyAnisotropic)
                GraphicsDevice.SamplerState[0].MinFilter = TextureFilter.Anisotropic;
            else if (GraphicsDevice.DeviceCaps.TextureFilterCaps.SupportsMinifyLinear)
                GraphicsDevice.SamplerState[0].MinFilter = TextureFilter.Linear;
        }

        /// <summary>
        /// Creates the Device Presentation Parameters
        /// </summary>
        /// <returns>A properly formatted Presentation Parameters</returns>
        private void CreatePresentParameters()
        {
            Screen currentScreen = Screen.FromRectangle(this.Bounds);

            CurrentAdapter = null;
            foreach (AdapterInformation a in Manager.Adapters)
                if (currentScreen.DeviceName.StartsWith(a.Information.DeviceName))
                {
                    CurrentAdapter = a;
                    break;
                }

            if (!Manager.CheckDeviceType(CurrentAdapter.Adapter, DeviceType.Hardware, CurrentAdapter.CurrentDisplayMode.Format, CurrentAdapter.CurrentDisplayMode.Format, Windowed != WindowMode.ExclusiveFullscreen))
            {
                throw new Exception("No hardware acceleration ? :(");
            }
            
            if (Windowed != WindowMode.Windowed)
                screenSize = currentScreen.Bounds.Size;
            
            currentPresentParameters = new PresentParameters()
            {
                SwapEffect = SwapEffect.Discard,
                PresentationInterval = PresentInterval.One,
                MultiSample = MultiSampleType.None,
                MultiSampleQuality = 0,
                Windowed = Windowed != WindowMode.ExclusiveFullscreen,
//                AutoDepthStencilFormat = DepthFormat.D16,
//                EnableAutoDepthStencil = true,
                BackBufferCount = 1,
                BackBufferFormat = Format.A8R8G8B8,
                BackBufferHeight = Windowed == WindowMode.Windowed ? windowSize.Height : screenSize.Height,
                BackBufferWidth = Windowed == WindowMode.Windowed ? windowSize.Width : screenSize.Width,
            };
        }

        /// <summary>
        /// Checks if the GraphicsDevice is ready to present.
        /// </summary>
        /// <returns>Ready to present?</returns>
        public bool DeviceAvailable()
        {
            if (GraphicsDevice == null || GraphicsDevice.Disposed)
                return false;
            
            int result;
            GraphicsDevice.CheckCooperativeLevel(out result);
            ResultCode r = (ResultCode)result;

            if (r == ResultCode.DeviceLost)
            {
                State = EngineState.Lost;
            }
            else if (r == ResultCode.DeviceNotReset)
            {
                ResetDevice();
            }
            else if (r == ResultCode.Success)
            {
                if (State == EngineState.Reset)
                    ResetDevice();
                else
                    State = EngineState.Ready;

                if (currentPresentParameters.Windowed != (Windowed != WindowMode.ExclusiveFullscreen))
                {
                    ResetDevice();
                    return false;
                }

                return true;
            }

            return false;
        }

        /// <summary>
        /// Resets the device.
        /// </summary>
        public void ResetDevice()
        {
            State = EngineState.Reset;

            if (WindowState == FormWindowState.Minimized || ClientSize.Height == 0 || ClientSize.Width == 0)
                return;

                if (BeforeReset != null)
                {
                    CancelEventArgs eventArgs = new CancelEventArgs(false);
                    BeforeReset(this, eventArgs);
                    if (eventArgs.Cancel)
                        return;
                }

                try
                {
                    if (!HandleVista())
                    {
                        State = EngineState.Reset;
                        return;
                    }
                    CreatePresentParameters();

                    if (Windowed == WindowMode.Windowed)
                    {
                        ClientSize = windowSize;
                        FormBorderStyle = FormBorderStyle.Sizable;
                        TopMost = false;
                        if(locationBeforeFullscreen != Point.Empty)
                            Location = locationBeforeFullscreen;
                        locationBeforeFullscreen = Point.Empty;
                    }
                    else if (Windowed == WindowMode.Fullscreen)
                    {
                        FormBorderStyle = FormBorderStyle.None;
                        TopMost = true;
                        Screen s = Screen.FromRectangle(Bounds);
                        Bounds = s.Bounds;
                        
                    }
                    

                    GraphicsDevice.Reset(currentPresentParameters);
                }
                catch (DeviceLostException)
                {
                    State = EngineState.Lost;
                    return;
                }
                catch (InvalidCallException)
                {
                    State = EngineState.Lost;
                    return;
                }
                State = EngineState.Ready;
                if (AfterReset != null)
                    AfterReset(this, EventArgs.Empty);


        }
        public bool HandleVista()
        {
            bool enabled;
            if(VistaControl.DwmIsCompositionEnabled(out enabled) != 0)
                return false;

            if (Environment.OSVersion.Version.Major >= 6) // if vista
            {
                if (Windowed == WindowMode.ExclusiveFullscreen && enabled)
                {
                    VistaControl.DwmEnableComposition(VistaControl.DWM_EC_DISABLECOMPOSITION);
                    return false;
                }
                else if(Windowed != WindowMode.ExclusiveFullscreen && !enabled)
                {
                    VistaControl.DwmEnableComposition(VistaControl.DWM_EC_ENABLECOMPOSITION);
                    return false;
                }
            }
            return true;
        }
        protected override void Dispose(bool disposing)
        {
            if (GraphicsDevice != null)
            {
                GraphicsDevice.Dispose();
                GraphicsDevice = null;
            }

            base.Dispose(disposing);
        }


        #region WndProc
        public delegate void WndProcHandler(ref Message m, ref bool callBase);
        public event WndProcHandler WindowsMessages;
        protected override void WndProc(ref Message m)
        {
            if (m.Msg == 0x112 && (m.WParam.ToInt32() == 0xF170 || m.WParam.ToInt32() == 0xF140))
            {
            // If screensaver wants to start, then dont!
            }
            else
            {
                bool callBase = true;
                if (WindowsMessages != null)
                    WindowsMessages(ref m, ref callBase);
                if(callBase)
                    base.WndProc(ref m);
            }
            
        }
        #endregion

        #region Application gejl

        private static bool AppStillIdle
        {
            get
            {
                PeekMsg msg;
                return !PeekMessage(out msg, IntPtr.Zero, 0, 0, 0);
            }
        }

        [StructLayout(LayoutKind.Sequential)]
        private struct PeekMsg
        {
            public IntPtr hWnd;
            public Message msg;
            public IntPtr wParam;
            public IntPtr lParam;
            public uint time;
            public Point p;
        }

        [SuppressUnmanagedCodeSecurity]
        [DllImport("User32.dll", CharSet = CharSet.Auto)]
        private static extern bool PeekMessage(out PeekMsg msg, IntPtr hWnd, uint messageFilterMin, uint messageFilterMax, uint flags);

        #endregion


        #region Engine Members

        public SizeS WindowSize
        {
            get
            {
                return new SizeS(windowSize.Width, windowSize.Height);
            }
            set
            {
                windowSize = new Size((int)value.Width, (int)value.Height);
            }
        }

        public SizeS CurrentSize
        {
            get
            {
                if (Windowed == WindowMode.Windowed)
                    return WindowSize;
                else
                    return new SizeS(screenSize.Width, screenSize.Height);
            }
        }


        public new PointS MousePosition
        {
            get
            {
                Point p = Cursor.Position;
                if (Windowed == WindowMode.Windowed)
                    p = PointToClient(p);
                return new PointS(p.X, p.Y);
            }
        }

        public void Reset()
        {
            ResetDevice();
        }

        public void Wait()
        {
            MDXUtil.Wait();
        } 

        public void Resume()
        {
            MDXUtil.Resume();
        }

        public TimeSpan TimeElapsed
        {
            get { return MDXUtil.TimeSpanElapsed; }
        }

        public bool IsCursorHidden
        {
            get { return mouseHider.CursorHidden; }
        }

        public bool StayOnTop
        {
            get
            {
                return this.TopMost;
            }
            set
            {
                Invoke(new MethodInvoker(delegate
                {
                    this.TopMost = value;
                }));
            }
        }

        #endregion
    }
}
